<!-- 审核流程插件基于https://gitee.com/xiaoka2017/easy-flow修改-->
<!--感谢萌级小菜鸟 / easy-flow -->
<template>
  <div v-if="easyFlowVisible" class="flow-panel">
    <div style="display: flex; height: 100%; position: relative">
      <el-scrollbar style="height: 100%; border-right: 1px solid rgb(220, 227, 232)">
        <div style="width: 220px">
          <div class="ef-node-pmenu-item">
            <i class="el-icon-warning-outline"></i>基础信息
          </div>
          <div :style="{ 'padding-left': $global.labelPosition == 'top' ? '0' : '10px' }">
            <VolForm
              ref="form"
              labelPosition="top"
              :label-width="180"
              :loadKey="true"
              :formFields="formFields"
              :formRules="formRules"
            ></VolForm>
          </div>

          <node-menu @addNode="addNode" ref="nodeMenu"></node-menu>
        </div>
      </el-scrollbar>
      <div class="tools">
        <el-button circle @click="zoomAdd"><i class="el-icon-zoom-in"></i></el-button>
        <el-button circle @click="zoomSub"><i class="el-icon-zoom-out"></i></el-button>
      </div>
      <div
        style="flex: 1"
        id="efContainer"
        ref="efContainer"
        class="container efContainer"
        v-flowDrag
      >
        <template :key="node.id" v-for="node in data.nodeList">
          <flow-node
            :id="node.id"
            @delNode="deleteNode(node.id)"
            :node="node"
            :activeElement="activeElement"
            @changeNodeSite="changeNodeSite"
            @nodeRightMenu="nodeRightMenu"
            @clickNode="clickNode"
          >
          </flow-node>
        </template>
        <!-- 给画布一个默认的宽度和高度 -->
        <div style="position: absolute; top: 3000px; left: 4000px">&nbsp;</div>
      </div>
      <!-- 右侧表单 -->
      <div
        style="width: 400px; border-left: 1px solid #dce3e8; background-color: #fbfbfb"
      >
        <el-scrollbar style="height: 100%; padding-bottom: 10px">
          <flow-node-form
            @delNode="deleteNode"
            ref="nodeForm"
            @setLineLabel="setLineLabel"
            @repaintEverything="repaintEverything"
          ></flow-node-form>
        </el-scrollbar>
      </div>
    </div>
  </div>
</template>

<script>
import { VueDraggableNext as draggable } from "vue-draggable-next";
// import { jsPlumb } from 'jsplumb'
// 使用修改后的jsplumb
import "./jsplumb";
import { easyFlowMixin } from "./mixins";
import flowNode from "./node";
import nodeMenu from "./node_menu";
import FlowNodeForm from "./node_form";
import lodash from "lodash";
// import { getDataA } from './data_A'
import VolForm from "@/components/basic/VolForm.vue";
export default {
  data() {
    return {
      formFields: {
        WorkName: "",
        WorkTable: "",
        WorkTableName: "",
        Weight: 1,
        AuditingEdit: 0,
        Remark: "",
      },
      formRules: [
        [
          {
            dataKey: "流程名称",
            title: "流程名称",
            field: "WorkName",
            required: true,
          },
        ],
        [
          {
            dataKey: "",
            title: "流程实例",
            required: true,
            field: "WorkTable",
            data: [],
            readonly: false,
            type: "select",
            onChange: (value, item) => {
              this.formRules.forEach((options) => {
                options.forEach((option) => {
                  if (option.field == "WorkTable") {
                    this.formFields.WorkTableName = option.data.find((x) => {
                      return x.key == value;
                    }).value;
                  }
                });
              });
            },
          },
        ],
        [
          {
            title: "权重(相同条件权重大优先)",
            field: "Weight",
            type: "number",
          },
        ],

        [
          {
            title: "同步更新审批业务数据",
            field: "AuditingEdit",
            type: "switch",
            data: [
              { key: 0, value: "否" },
              { key: 1, value: "是" },
            ],
          },
        ],
        [
          {
            title: "备注",
            field: "Remark",
          },
        ],
      ],
      // jsPlumb 实例
      jsPlumb: null,
      // 控制画布销毁
      easyFlowVisible: true,
      // 是否加载完毕标志位
      loadEasyFlowFinish: false,
      // 数据
      data: {},
      // 激活的元素、可能是节点、可能是连线
      activeElement: {
        // 可选值 node 、line
        type: undefined,
        // 节点ID
        nodeId: undefined,
        // 连线ID
        sourceId: undefined,
        targetId: undefined,
      },
      zoom: 1,
    };
  },
  // 一些基础配置移动该文件中
  mixins: [easyFlowMixin],
  components: {
    draggable,
    flowNode,
    nodeMenu,
    FlowNodeForm,
    VolForm,
  },
  directives: {
    flowDrag: {
      mounted(el, binding, vnode, oldNode) {
        if (!binding) {
          return;
        }
        el.onmousedown = (e) => {
          if (e.button == 2) {
            // 右键不管
            return;
          }
          //  鼠标按下，计算当前原始距离可视区的高度
          let disX = e.clientX;
          let disY = e.clientY;
          el.style.cursor = "move";

          document.onmousemove = function (e) {
            // 移动时禁止默认事件
            e.preventDefault();
            const left = e.clientX - disX;
            disX = e.clientX;
            el.scrollLeft += -left;

            const top = e.clientY - disY;
            disY = e.clientY;
            el.scrollTop += -top;
          };

          document.onmouseup = function (e) {
            el.style.cursor = "auto";
            document.onmousemove = null;
            document.onmouseup = null;
          };
        };
      },
    },
  },
  mounted() {
    this.jsPlumb = jsPlumb.getInstance();
    // this.$nextTick(() => {
    //     // 默认加载流程A的数据、在这里可以根据具体的业务返回符合流程数据格式的数据即可
    //     this.dataReload(getDataA())
    // })
  },
  created() {
    this.http.get("api/Sys_WorkFlow/getTableInfo").then((result) => {
      this.formRules.forEach((options) => {
        options.forEach((option) => {
          if (option.field == "WorkTable") {
            option.data = result;
          }
        });
      });
    });
    this.$store.getters.data().flowTable = this.formFields;
  },
  methods: {
    // 返回唯一标识
    getUUID() {
      return Math.random().toString(36).substr(3, 10);
    },
    jsPlumbInit() {
      this.jsPlumb.ready(() => {
        // 导入默认配置
        this.jsPlumb.importDefaults(this.jsplumbSetting);
        // 会使整个jsPlumb立即重绘。
        this.jsPlumb.setSuspendDrawing(false, true);
        // 初始化节点
        this.loadEasyFlow();
        // 单点击了连接线, https://www.cnblogs.com/ysx215/p/7615677.html
        this.jsPlumb.bind("click", (conn, originalEvent) => {
          this.activeElement.type = "line";
          this.activeElement.sourceId = conn.sourceId;
          this.activeElement.targetId = conn.targetId;
          this.$refs.nodeForm.lineInit({
            from: conn.sourceId,
            to: conn.targetId,
            label: conn.getLabel(),
          });
          this.deleteElement();
        });
        // 连线
        this.jsPlumb.bind("connection", (evt) => {
          let from = evt.source.id;
          let to = evt.target.id;
          if (this.loadEasyFlowFinish) {
            this.data.lineList.push({ from: from, to: to });
          }
        });

        // 删除连线回调
        this.jsPlumb.bind("connectionDetached", (evt) => {
          this.deleteLine(evt.sourceId, evt.targetId);
        });

        // 改变线的连接节点
        this.jsPlumb.bind("connectionMoved", (evt) => {
          this.changeLine(evt.originalSourceId, evt.originalTargetId);
        });

        // 连线右击
        this.jsPlumb.bind("contextmenu", (evt) => {
          console.log("contextmenu", evt);
        });

        // 连线
        this.jsPlumb.bind("beforeDrop", (evt) => {
          let from = evt.sourceId;
          let to = evt.targetId;
          if (from === to) {
            this.$message.error("节点不支持连接自己");
            return false;
          }
          if (this.hasLine(from, to)) {
            this.$message.error("该关系已存在,不允许重复创建");
            return false;
          }
          if (this.hashOppositeLine(from, to)) {
            this.$message.error("不支持两个节点之间连线回环");
            return false;
          }
          this.$message.success("连接成功");
          setTimeout(() => {
            this.setLineLabel(from, to, "x");
          }, 50);
          return true;
        });

        // beforeDetach
        this.jsPlumb.bind("beforeDetach", (evt) => {
          console.log("beforeDetach", evt);
        });
        this.jsPlumb.setContainer(this.$refs.efContainer);
      });
    },
    // 加载流程图
    loadEasyFlow() {
      // 初始化节点
      for (var i = 0; i < this.data.nodeList.length; i++) {
        let node = this.data.nodeList[i];
        // 设置源点，可以拖出线连接其他节点
        this.jsPlumb.makeSource(node.id, lodash.merge(this.jsplumbSourceOptions, {}));
        // // 设置目标点，其他源点拖出的线可以连接该节点
        this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions);
        if (!node.viewOnly) {
          this.jsPlumb.draggable(node.id, {
            containment: "parent",
            stop: function (el) {
              // 拖拽节点结束后的对调
              console.log("拖拽结束: ", el);
            },
          });
        }
      }
      // 初始化连线
      for (var i = 0; i < this.data.lineList.length; i++) {
        let line = this.data.lineList[i];
        var connParam = {
          source: line.from,
          target: line.to,
          label: line.label ? line.label : "x",
          connector: line.connector ? line.connector : "",
          anchors: line.anchors ? line.anchors : undefined,

          paintStyle: line.paintStyle ? line.paintStyle : undefined,
        };
        this.jsPlumb.connect(connParam, this.jsplumbConnectOptions);
      }
      this.$nextTick(function () {
        this.loadEasyFlowFinish = true;
      });
    },
    // 设置连线条件
    setLineLabel(from, to, label) {
      var conn = this.jsPlumb.getConnections({
        source: from,
        target: to,
      })[0];
      if (!label || label === "") {
        conn.removeClass("flowLabel ");
        conn.addClass("emptyFlowLabel");
      } else {
        conn.addClass("flowLabel");
      }
      conn.setLabel({
        label: "x", //label,
      });
      this.data.lineList.forEach(function (line) {
        if (line.from == from && line.to == to) {
          line.label = "x"; // label
        }
      });
    },
    // 删除激活的元素
    deleteElement() {
      if (this.activeElement.type === "node") {
        this.deleteNode(this.activeElement.nodeId);
      } else if (this.activeElement.type === "line") {
        this.$confirm("确定删除所点击的线吗?", "提示", {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        })
          .then(() => {
            var conn = this.jsPlumb.getConnections({
              source: this.activeElement.sourceId,
              target: this.activeElement.targetId,
            })[0];
            this.jsPlumb.deleteConnection(conn);
          })
          .catch(() => {});
      }
    },
    // 删除线
    deleteLine(from, to) {
      this.data.lineList = this.data.lineList.filter(function (line) {
        if (line.from == from && line.to == to) {
          return false;
        }
        return true;
      });
    },
    // 改变连线
    changeLine(oldFrom, oldTo) {
      this.deleteLine(oldFrom, oldTo);
    },
    // 改变节点的位置
    changeNodeSite(data) {
      for (var i = 0; i < this.data.nodeList.length; i++) {
        let node = this.data.nodeList[i];
        if (node.id === data.nodeId) {
          node.left = data.left;
          node.top = data.top;
        }
      }
    },
    /**
     * 拖拽结束后添加新的节点
     * @param evt
     * @param nodeMenu 被添加的节点对象
     * @param mousePosition 鼠标拖拽结束的坐标
     */
    addNode(evt, nodeMenu, mousePosition) {
      if (
        nodeMenu.type == "start" &&
        this.data.nodeList.some((x) => {
          return x.type == "start";
        })
      ) {
        this.$message.error("【流程结束】节点已存在,只有选择一个流程开始节点");
        return;
      }
      if (
        nodeMenu.type == "end" &&
        this.data.nodeList.some((x) => {
          return x.type == "end";
        })
      ) {
        this.$message.error("【流程结束】节点已存在,只有选择一个流程开始节点");
        return;
      }
      var screenX = evt.originalEvent.clientX,
        screenY = evt.originalEvent.clientY;
      let efContainer = this.$refs.efContainer;
      var containerRect = efContainer.getBoundingClientRect();
      var left = screenX,
        top = screenY;
      // 计算是否拖入到容器中
      if (
        left < containerRect.x ||
        left > containerRect.width + containerRect.x ||
        top < containerRect.y ||
        containerRect.y > containerRect.y + containerRect.height
      ) {
        this.$message.error("请把节点拖入到画布中");
        return;
      }
      left = left - containerRect.x + efContainer.scrollLeft;
      top = top - containerRect.y + efContainer.scrollTop;
      // 居中
      left -= 85;
      top -= 16;
      var nodeId = this.getUUID();
      // 动态生成名字
      var origName = nodeMenu.name;
      var nodeName = origName;
      var index = 1;
      while (index < 10000) {
        var repeat = false;
        for (var i = 0; i < this.data.nodeList.length; i++) {
          let node = this.data.nodeList[i];
          if (node.name === nodeName) {
            nodeName = origName + index;
            repeat = true;
          }
        }
        if (repeat) {
          index++;
          continue;
        }
        break;
      }
      var node = {
        id: nodeId,
        name: nodeName,
        type: nodeMenu.type,
        left: left + "px",
        top: top + "px",
        ico: nodeMenu.ico,
        state: "success",
      };
      /**
       * 这里可以进行业务判断、是否能够添加该节点
       */
      this.data.nodeList.push(node);
      this.$nextTick(function () {
        this.jsPlumb.makeSource(nodeId, this.jsplumbSourceOptions);
        this.jsPlumb.makeTarget(nodeId, this.jsplumbTargetOptions);
        this.jsPlumb.draggable(nodeId, {
          containment: "parent",
          stop: function (el) {
            // 拖拽节点结束后的对调
            console.log("拖拽结束: ", el);
          },
        });
      });
    },
    /**
     * 删除节点
     * @param nodeId 被删除节点的ID
     */
    deleteNode(nodeId) {
      this.$confirm("确定要删除节点" + nodeId + "?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
        closeOnClickModal: false,
      })
        .then(() => {
          /**
           * 这里需要进行业务判断，是否可以删除
           */
          this.data.nodeList = this.data.nodeList.filter(function (node) {
            if (node.id === nodeId) {
              // 伪删除，将节点隐藏，否则会导致位置错位
              // node.show = false
              return false;
            }
            return true;
          });
          this.$nextTick(function () {
            this.jsPlumb.removeAllEndpoints(nodeId);
          });
        })
        .catch(() => {});
      return true;
    },
    clickNode(nodeId) {
      this.activeElement.type = "node";
      this.activeElement.nodeId = nodeId;
      this.$refs.nodeForm.nodeInit(this.data, nodeId, this.formFields.WorkTable);
    },
    // 是否具有该线
    hasLine(from, to) {
      for (var i = 0; i < this.data.lineList.length; i++) {
        var line = this.data.lineList[i];
        if (line.from === from && line.to === to) {
          return true;
        }
      }
      return false;
    },
    // 是否含有相反的线
    hashOppositeLine(from, to) {
      return this.hasLine(to, from);
    },
    nodeRightMenu(nodeId, evt) {
      this.menu.show = true;
      this.menu.curNodeId = nodeId;
      this.menu.left = evt.x + "px";
      this.menu.top = evt.y + "px";
    },
    repaintEverything(node) {
      let _node = this.data.nodeList.find((x) => {
        return x.id == node.id;
      });
      Object.assign(_node, node);
      console.log(_node);
      this.jsPlumb.repaint();
    },
    // 加载流程图
    dataReload(data, isAdd) {
      this.easyFlowVisible = false;
      this.data.nodeList = [];
      this.data.lineList = [];
      this.$nextTick(() => {
        data = lodash.cloneDeep(data);
        this.easyFlowVisible = true;
        this.data = data;
        this.$nextTick(() => {
          this.jsPlumb = jsPlumb.getInstance();
          this.$nextTick(() => {
            this.jsPlumbInit();
          });
        });
      });
      this.formRules.forEach((options) => {
        options.forEach((option) => {
          if (option.field == "WorkTable") {
            option.readonly = !isAdd;
          }
        });
      });
    },
    zoomAdd() {
      if (this.zoom >= 1) {
        return;
      }
      this.zoom = this.zoom + 0.1;
      this.$refs.efContainer.style.zoom = this.zoom;
      // this.jsPlumb.setZoom(this.zoom)
    },
    zoomSub() {
      if (this.zoom <= 0) {
        return;
      }
      this.zoom = this.zoom - 0.1;
      if (this.zoom < 0.3) {
        this.zoom = 0.3;
      }
      this.$refs.efContainer.style.zoom = this.zoom;
      // this.jsPlumb.setZoom(this.zoom)
    },
  },
};
</script>
<style scoped lang="less">
@import "./index.css";
.ef-node-pmenu-item {
  padding: 10px;
  background: #f8f8f8;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 1px;
  border-top: 1px solid #eee;
  border-bottom: 1px solid #eee;
  border-right: 1px solid #eee;
}

.flow-panel {
  position: absolute;
  height: 100%;
  width: 100%;
}

.flow-panel ::v-deep(.el-form-item__label) {
  margin-bottom: -2px !important;
  text-align: left;
  padding: 0 !important;
  justify-content: flex-start;
}

.flow-panel ::v-deep(.el-form-item) {
  display: flex;
  flex-direction: column;
  margin-bottom: 7px !important;
}

.ef-node-menu-form {
  padding: 0px;
}

::-webkit-scrollbar {
  width: 0px;
  height: 0px;
}

::-webkit-scrollbar-thumb {
  border-radius: 0px;
  background: #e0e3e7;
  height: 20px;
}

::-webkit-scrollbar-track {
  background-color: transparent;
}
</style>
